/**
 * @Author: YangYun
 * @Date: 2020/7/22 11:12
 */
package lib

import (
	"crypto/aes"
	"encoding/base64"
	"encoding/json"
	"errors"
	"fmt"
	rotatelogs "github.com/lestrrat-go/file-rotatelogs"
	"io"
	"io/ioutil"
	"log"
	"math/rand"
	"net/http"
	"net/url"
	"os"
	"strings"
	"time"

	"git.900sui.cn/kc/base/common/functions"
	"git.900sui.cn/kc/kcgin"
	pay2 "git.900sui.cn/kc/rpcinterface/interface/pay"
	"github.com/shopspring/decimal"
	"github.com/tidwall/gjson"
	"github.com/wenzhenxi/gorsa"
)

const (
	allchar = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"

	FUND_FLAG_suc  = "0"
	FUND_FLAG_fail = "1"
	FUND_FLAG_ing  = "2"
)

type sandResultInterface interface {
	GetRespCode() string
	GetRespDesc() string
}
type sandResult struct {
	RespCode string `json:"respCode"`
	RespDesc string `json:"respDesc"`
}

func (s *sandResult) GetRespCode() string {
	return s.RespCode
}

func (s *sandResult) GetRespDesc() string {
	return s.RespDesc
}

type sandpay struct {
	host      string
	h5host    string
	daifuhost string

	privateKey          string
	sandpayPublicKey    string
	sandpayPublicKeyApp string

	mid       string
	notifyUrl string
	frontUrl  string

	payTimeOut time.Duration

	agent map[string]string
}

var Sandpay *sandpay

func init() {
	Sandpay = &sandpay{
		host:      "https://cashier.sandpay.com.cn/gateway/api",
		h5host:    "https://cashier.sandpay.com.cn/gw/web",
		daifuhost: "https://caspay.sandpay.com.cn/agent-main/openapi",

		mid:                 kcgin.AppConfig.String("sandpay.mid"),
		notifyUrl:           kcgin.AppConfig.String("sandpay.notifyUrl"),
		frontUrl:            kcgin.AppConfig.String("sandpay.frontUrl") + "/#/serve/consumeResult",
		privateKey:          functions.GetPemPrivate(kcgin.AppConfig.String("sandpay.privateKey")),
		sandpayPublicKey:    functions.GetPemPublic(kcgin.AppConfig.String("sandpay.sandpayPublicKey")),
		sandpayPublicKeyApp: functions.GetPemPublic(kcgin.AppConfig.String("sandpay.sandpayPublicKeyApp")),

		payTimeOut: time.Duration(kcgin.AppConfig.DefaultInt64("pay.timeout", 1800) * int64(time.Second)),

		agent: map[string]string{
			"/queryBalance":     "MBQU", // 余额查询
			"/agentpay":         "RTPM", // 实时代付
			"/queryOrder":       "ODQU", // 订单查询
			"/queryAgentpayFee": "PTFQ", // 代付手续费查询
		},
	}
}

// 二维码支付
// @orderSn 订单编号
// @amount 金额
// @title 支付标题
// @body 支付内容
// @formUrl 前端跳转连接
// @return string 二维码内容
func (s *sandpay) QrCode(orderSn string, amount decimal.Decimal, title string, body string, formUrl string) (string, error) {
	now := time.Now()
	data := map[string]interface{}{
		"head": map[string]interface{}{
			"version":     "1.0",
			"method":      "sandpay.trade.precreate",
			"productId":   "00000012",
			"accessType":  "1",
			"mid":         s.mid,
			"channelType": "07",
			"reqTime":     now.Format("20060102150405"),
		},
		"body": map[string]interface{}{
			"payTool":     "0401",
			"orderCode":   orderSn,
			"totalAmount": fmt.Sprintf("%012v", amount.Mul(decimal.NewFromInt(100))),
			"subject":     title,
			"body":        body,
			"txnTimeOut":  now.Add(s.payTimeOut).Format("20060102150405"),
			"notifyUrl":   s.notifyUrl,
			"formUrl":     formUrl,
			"extend":      "",
		},
	}

	res, err := s.exec("/order/create", data)
	if err != nil {
		return "", err
	}
	if res.Get("qrCode").Exists() {
		return res.Get("qrCode").String(), nil
	}
	return "", errors.New("system error")
}

// 微信小程序支付
// @orderSn 订单编号
// @amount 金额
// @title 支付标题
// @body 支付内容
// @formUrl 前端跳转连接
// @appid 小程序appid
// @openid 支付用户openid
// @return *gjson.Result 小程序支付所需内容
func (s *sandpay) WxApp(orderSn string, amount decimal.Decimal, title string, body string, formUrl string, appid string, openid string) (*gjson.Result, error) {
	now := time.Now()
	payExtra, _ := json.Marshal(map[string]string{
		"subAppid": appid,
		"userId":   openid,
	})
	data := map[string]interface{}{
		"head": map[string]interface{}{
			"version":     "1.0",
			"method":      "sandpay.trade.pay",
			"productId":   "00002021",
			"accessType":  "1",
			"mid":         s.mid,
			"channelType": "07",
			"reqTime":     now.Format("20060102150405"),
		},
		"body": map[string]interface{}{
			"orderCode":   orderSn,
			"totalAmount": fmt.Sprintf("%012v", amount.Mul(decimal.NewFromInt(100))),
			"subject":     title,
			"body":        body,
			"txnTimeOut":  now.Add(s.payTimeOut).Format("20060102150405"),
			"notifyUrl":   s.notifyUrl,
			"formUrl":     formUrl,
			"clientIp":    "127.0.0.1",
			"payMode":     "sand_wx",
			"payExtra":    string(payExtra),
			"extend":      "",
		},
	}
	res, err := s.exec("/order/pay", data)
	if err != nil {
		return nil, err
	}
	return res, nil
}

// h5支付
// @orderSn 订单编号
// @amount 金额
// @title 支付标题
// @body 支付内容
// @formUrl 前端跳转连接
// @return string 跳转连接
func (s *sandpay) H5(orderSn string, amount decimal.Decimal, title string, body string, formUrl string) (string, error) {
	now := time.Now()
	data := map[string]interface{}{
		"head": map[string]interface{}{
			"version":     "1.0",
			"method":      "sandpay.trade.orderCreate",
			"productId":   "00002000",
			"accessType":  "1",
			"mid":         s.mid,
			"channelType": "07",
			"reqTime":     now.Format("20060102150405"),
		},
		"body": map[string]interface{}{
			"orderCode":   orderSn,
			"totalAmount": fmt.Sprintf("%012v", amount.Mul(decimal.NewFromInt(100))),
			"subject":     title,
			"body":        body,
			"txnTimeOut":  now.Add(s.payTimeOut).Format("20060102150405"),
			"payModeList": "[alipay,wechatpay,unionpay]",
			"notifyUrl":   s.notifyUrl,
			"formUrl":     formUrl,
			"extend":      "",
		},
	}
	postData, err := s.getPostData(data)
	return s.host + "/order/create?" + postData, err
}

func (s *sandpay) H5New(orderSn string, amount decimal.Decimal, title string, body string, formURL string, p *pay2.PayH5) (err error) {
	now := time.Now()
	data := map[string]interface{}{
		"head": map[string]interface{}{
			"version":     "1.0",
			"method":      "sandpay.trade.orderCreate",
			"productId":   "00002000",
			"accessType":  "1",
			"mid":         s.mid,
			"channelType": "07",
			"reqTime":     now.Format("20060102150405"),
		},
		"body": map[string]interface{}{
			"orderCode":   orderSn,
			"totalAmount": fmt.Sprintf("%012v", amount.Mul(decimal.NewFromInt(100))),
			"subject":     title,
			"body":        body,
			"txnTimeOut":  now.Add(s.payTimeOut).Format("20060102150405"),
			"payModeList": "[alipay,wechatpay,unionpay]",
			"notifyUrl":   s.notifyUrl,
			"formUrl":     formURL,
			"frontUrl":    s.frontUrl,
			"extend":      "",
		},
	}
	postData, err := s.getPostDataMap(data)

	p.PaySign = postData["sign"]
	p.PayURL = s.h5host + "/order/create"
	p.PayBoby = postData["data"]
	return
}

type queryBlanch struct {
	sandResult
	Balance string `json:"balance"`
}

// 余额查询
// @orderSn 订单编号
// @return *decimal.Decimal 余额
func (s *sandpay) QueryBlanch(orderSn string) (*decimal.Decimal, error) {
	data := map[string]interface{}{
		"version":   "01",
		"productId": "00000003",
		"tranTime":  time.Now().Format("20060102150405"),
		"orderCode": orderSn,
	}
	result := queryBlanch{}
	err := s.execAgent("/queryBalance", data, &result)
	if err != nil {
		return nil, err
	}
	balance, err := decimal.NewFromString(result.Balance)
	if err != nil {
		return nil, err
	}
	balance = balance.Div(decimal.NewFromInt(100))
	return &balance, nil
}

type agentPay struct {
	sandResult
	TranTime   string `json:"tranTime"`
	ResultFlag string `json:"resultFlag"`
	TranFee    string `json:"tranFee"`
}

func (a *agentPay) GetFee() decimal.Decimal {
	fee, _ := decimal.NewFromString(a.TranFee)
	return fee.Div(decimal.NewFromInt(100))
}

// 单笔代付/清分
// @orderSn string 订单编号
// @amount decimal.Decimal 清分金额
// @cardNo string 账户账号
// @cardName string 账户户名
// @bankName string 账户开户行
// @bankType string 账户开户行联行号
// @return string 订单发起时间 格式YmdHis
// @return *gjson.Result 代付/清分结果
func (s *sandpay) AgentPay(orderSn string, amount decimal.Decimal, cardNo string, cardName string, bankName string, bankType string) (result agentPay, err error) {
	tranTime := time.Now()
	data := map[string]interface{}{
		"orderCode":    orderSn,
		"version":      "01",
		"productId":    "00000003",
		"tranTime":     tranTime.Format("20060102150405"),
		"tranAmt":      fmt.Sprintf("%012v", amount.Mul(decimal.NewFromInt(100))),
		"currencyCode": "156",
		"accAttr":      "1",
		"accType":      "3",
		"accNo":        cardNo,
		"accName":      cardName,
		"bankName":     bankName,
		"bankType":     bankType,
		"remark":       "资金清分",
		"payMode":      "1",
		"channelType":  "07",
		"noticeUrl":    s.notifyUrl,
	}
	//记录日志
	file, _ := rotatelogs.New(kcgin.AppConfig.String("sandpay.agentPay"))
	Logers := log.New(io.MultiWriter(file, os.Stderr),
		strings.ToUpper("INFO")+"：",
		log.Ldate|log.Ltime|log.Lshortfile)
	requestLogDataBytes,_ := json.Marshal(data)
	Logers.Printf("===requestData:%v===",string(requestLogDataBytes[:]))
	err = s.execAgent("/agentpay", data, &result)
	responseLogDataBytes,_ := json.Marshal(result)
	Logers.Printf("===responseData:%v===",string(responseLogDataBytes[:]))
	return
}

type agentQuery struct {
	sandResult
	TranTime   string `json:"tranTime"`
	ResultFlag string `json:"resultFlag"`
	TranFee    string `json:"tranFee"`
}

func (a *agentQuery) GetFee() decimal.Decimal {
	fee, _ := decimal.NewFromString(a.TranFee)
	return fee.Div(decimal.NewFromInt(100))
}

// 查询代付/清分结果
// orderSn string 订单编号
// tranTime string 代付/清分时间 格式 YmdHis
func (s *sandpay) AgentQuery(orderSn string, tranTime string) (result agentQuery, err error) {
	data := map[string]interface{}{
		"orderCode": orderSn,
		"version":   "01",
		"productId": "00000003",
		"tranTime":  tranTime,
	}
	file, _ := rotatelogs.New(kcgin.AppConfig.String("sandpay.agentQuery"))
	Logers := log.New(io.MultiWriter(file, os.Stderr),
		strings.ToUpper("INFO")+"：",
		log.Ldate|log.Ltime|log.Lshortfile)
	requestLogDataBytes,_ := json.Marshal(data)
	Logers.Printf("===requestData:%v===",string(requestLogDataBytes[:]))
	err = s.execAgent("/queryOrder", data, &result)
	responseLogDataBytes,_ := json.Marshal(result)
	Logers.Printf("===responseData:%v===",string(responseLogDataBytes[:]))
	return
}

type agentFee struct {
	sandResult
	OrderCode string `json:"orderCode"`
	TranFee   string `json:"tranFee"`
}

func (a *agentFee) GetFee() decimal.Decimal {
	fee, _ := decimal.NewFromString(a.TranFee)
	return fee.Div(decimal.NewFromInt(100))
}

// 查询代付/清分手续费
// orderSn string 订单编号
// amount decimal.Decimal 代付/清分金额
// cardNo string 代付/清分账号
func (s *sandpay) AgentFee(orderSn string, amount decimal.Decimal, cardNo string) (result agentFee, err error) {
	data := map[string]interface{}{
		"orderCode":    orderSn,
		"version":      "01",
		"productId":    "00000003",
		"tranTime":     time.Now().Format("20060102150405"),
		"tranAmt":      fmt.Sprintf("%012v", amount.Mul(decimal.NewFromInt(100))),
		"currencyCode": "156",
		"accAttr":      "1",
		"accType":      "3",
		"accNo":        cardNo,
	}
	file, _ := rotatelogs.New(kcgin.AppConfig.String("sandpay.agentFee"))
	Logers := log.New(io.MultiWriter(file, os.Stderr),
		strings.ToUpper("INFO")+"：",
		log.Ldate|log.Ltime|log.Lshortfile)
	requestLogDataBytes,_ := json.Marshal(data)
	Logers.Printf("===requestData:%v===",string(requestLogDataBytes[:]))
	err = s.execAgent("/queryAgentpayFee", data, &result)
	responseLogDataBytes,_ := json.Marshal(result)
	Logers.Printf("===responseData:%v===",string(responseLogDataBytes[:]))
	return
}

// 异步回调 InputData raw数据
func (s *sandpay) Notify(inputData string) (*gjson.Result, error) {
	dataJson := gjson.Parse(inputData)
	var head, body gjson.Result
	if dataJson.Exists() {
		// app支付
		signData := fmt.Sprintf("body=%s&head=%s", dataJson.Get("body").String(), dataJson.Get("head").String())
		if err := s.verifyApp(signData, dataJson.Get("sign").String()); err != nil {
			return nil, err
		}
		head = gjson.Parse(dataJson.Get("head").String())
		body = gjson.Parse(dataJson.Get("body").String())
	} else {
		//dataValues := s.parseResult(inputData)
		dataValues, err := url.ParseQuery(inputData)
		if err != nil {
			return nil, err
		}
		if err := s.verify(dataValues.Get("data"), dataValues.Get("sign")); err != nil {
			return nil, err
		}
		res := gjson.Parse(dataValues.Get("data"))
		head = res.Get("head")
		body = res.Get("body")
	}
	if head.Get("respCode").String() != "000000" {
		return nil, errors.New(head.Get("respMsg").String())
	}
	return &body, nil

}

// 签名
func (s *sandpay) signature(data string) (string, error) {
	return gorsa.SignSha1WithRsa(data, s.privateKey)
}

// 验签
func (s *sandpay) verify(data string, sign string) error {
	return gorsa.VerifySignSha1WithRsa(data, sign, s.sandpayPublicKey)
}

// 验签app
func (s *sandpay) verifyApp(data string, sign string) error {
	return gorsa.VerifySignSha256WithRsa(data, sign, s.sandpayPublicKeyApp)
}

// 生成aesKey
func (s *sandpay) aesKey(size int) string {
	res := ""
	for i := 0; i < size; i++ {
		res += string(allchar[rand.Intn(62)])
	}
	return res
}

// Key 编码
func (s *sandpay) encodeKey(key string) (string, error) {
	return gorsa.PublicEncrypt(key, s.sandpayPublicKey)
}

// aes 加密
func (s *sandpay) aesEncrypt(origData []byte, key []byte) string {
	ciphers, _ := aes.NewCipher(s.generateKey(key))
	length := (len(origData) + aes.BlockSize) / aes.BlockSize
	plain := make([]byte, length*aes.BlockSize)
	copy(plain, origData)
	pad := byte(len(plain) - len(origData))
	for i := len(origData); i < len(plain); i++ {
		plain[i] = pad
	}
	encrypted := make([]byte, len(plain))
	// 分组分块加密
	for bs, be := 0, ciphers.BlockSize(); bs <= len(origData); bs, be = bs+ciphers.BlockSize(), be+ciphers.BlockSize() {
		ciphers.Encrypt(encrypted[bs:be], plain[bs:be])
	}

	return base64.StdEncoding.EncodeToString(encrypted)
}

// aes 解密
func (s *sandpay) aesDecrypt(data string, key string) (string, error) {
	decrypt, err := gorsa.PriKeyDecrypt(key, s.privateKey)
	if err != nil {
		return "", err
	}
	byteKey := []byte(decrypt)
	byteData, err := base64.StdEncoding.DecodeString(data)
	ciphers, _ := aes.NewCipher(s.generateKey(byteKey))
	decrypted := make([]byte, len(byteData))
	for bs, be := 0, ciphers.BlockSize(); bs < len(byteData); bs, be = bs+ciphers.BlockSize(), be+ciphers.BlockSize() {
		ciphers.Decrypt(decrypted[bs:be], byteData[bs:be])
	}
	trim := 0
	if len(decrypted) > 0 {
		trim = len(decrypted) - int(decrypted[len(decrypted)-1])
	}
	return string(decrypted[:trim]), nil
}

//
func (s *sandpay) generateKey(key []byte) (genKey []byte) {
	genKey = make([]byte, 16)
	copy(genKey, key)
	for i := 16; i < len(key); {
		for j := 0; j < 16 && i < len(key); j, i = j+1, i+1 {
			genKey[j] ^= key[i]
		}
	}
	return genKey
}

// 获取提交数据
func (s *sandpay) getPostData(data map[string]interface{}) (string, error) {
	marshal, err := json.Marshal(data)
	if err != nil {
		return "", err
	}
	sign, err := s.signature(string(marshal))
	if err != nil {
		return "", err
	}
	return url.Values{
		"charset":  []string{"utf-8"},
		"signType": []string{"01"},
		"data":     []string{string(marshal)},
		"sign":     []string{sign},
	}.Encode(), nil
}

func (s *sandpay) getPostDataMap(data map[string]interface{}) (mp map[string]string, err error) {
	marshal, err := json.Marshal(data)
	if err != nil {
		return mp, err
	}
	sign, err := s.signature(string(marshal))
	if err != nil {
		return mp, err
	}

	return map[string]string{
		"charset":  "utf-8",
		"signType": "01",
		"data":     string(marshal),
		"sign":     sign,
	}, nil
}

// postData处理
func (s *sandpay) parseResult(result string) url.Values {
	unescape, err := url.PathUnescape(result)
	// 去除首尾引号
	unescape = strings.Trim(unescape, "\"")
	// +号会被转义
	unescape = strings.ReplaceAll(unescape, "+", "%2B")
	query, err := url.ParseQuery(unescape)
	if err != nil {
		return nil
	}
	return query
}

// 执行
func (s *sandpay) exec(action string, data map[string]interface{}) (*gjson.Result, error) {
	postData, err := s.getPostData(data)
	if err != nil {
		return nil, err
	}
	result, err := s.request("post", s.host+action, postData, &map[string]string{"Content-Type": "application/x-www-form-urlencoded"})
	res := s.parseResult(result)
	if err := s.verify(res.Get("data"), res.Get("sign")); err != nil {
		return nil, err
	}
	resJson := gjson.Parse(res.Get("data"))
	if resJson.Get("head.respCode").String() != "000000" {
		return nil, errors.New(resJson.Get("head.respMsg").String())
	}
	body := resJson.Get("body")
	return &body, nil
}

// 分账执行
func (s *sandpay) execAgent(action string, data map[string]interface{}, result sandResultInterface) error {
	info, err := json.Marshal(data)
	if err != nil {
		return err
	}
	key := s.aesKey(16)
	enKey, err := s.encodeKey(key)
	if err != nil {
		return err
	}
	signature, err := s.signature(string(info))
	if err != nil {
		return err
	}
	postData := url.Values{
		"transCode":   []string{s.agent[action]},
		"accessType":  []string{"0"},
		"merId":       []string{s.mid},
		"encryptKey":  []string{enKey},
		"encryptData": []string{s.aesEncrypt(info, []byte(key))},
		"sign":        []string{signature},
	}.Encode()
	resultStr, err := s.request("post", s.daifuhost+action, postData, &map[string]string{"Content-Type": "application/x-www-form-urlencoded"})

	res := s.parseResult(resultStr)
	resData, err := s.aesDecrypt(res.Get("encryptData"), res.Get("encryptKey"))
	if err != nil {
		return err
	}
	if err := s.verify(resData, res.Get("sign")); err != nil {
		return err
	}
	if err := json.Unmarshal([]byte(resData), result); err != nil {
		return err
	}
	if result.GetRespCode() != "0000" {
		return errors.New(result.GetRespDesc())
	}
	return nil
}

// 网络请求
func (s *sandpay) request(method string, host string, data string, headers ...*map[string]string) (string, error) {
	client := &http.Client{}
	method = strings.ToUpper(method)

	req, err := http.NewRequest(method, host, strings.NewReader(data))
	if err != nil {
		return "", err
	}
	if len(headers) > 0 {
		for key, value := range *headers[0] {
			req.Header.Add(key, value)
		}
	}
	resp, err := client.Do(req)
	if err != nil {
		return "", err
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return "", err
	}
	return string(body), nil
}
